/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#include "pch.h"
#include "page_cache_manager.h"
#include <string>
#include <journal/models/internal_interfaces.h>

using namespace journal::model;

page_cache_manager::command_object::command_object() :
    state(command_state::command_not_started),
    priority(priority_type::priority_last),
    targetLevel(page_level::page_none),
    dependencyCount(0),
    dependentId(0)
{
    static b::uint32 maxId = 1;
    id = maxId++;
}

page_cache_manager::command_object::~command_object()
{
    dump(L"destructing");
}

void page_cache_manager::command_object::dump(std::wstring prefix)
{
    std::wstring priorityString;
    switch(priority)
    {
    case priority_thumbnail     : priorityString = L"priority_thumbnail"; break;
    case priority_load_pass_0   : priorityString = L"priority_load_pass_0"; break;
    case priority_load_pass_1   : priorityString = L"priority_load_pass_1"; break;
    case priority_load_pass_2   : priorityString = L"priority_load_pass_2"; break;
    case priority_last          : priorityString = L"priority_last"; break;
    case priority_next_command_0: priorityString = L"priority_next_command_0"; break;
    case priority_next_command_1: priorityString = L"priority_next_command_1"; break;
    case priority_next_command_2: priorityString = L"priority_next_command_2"; break;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! [%s] command Object : command id = %i : page id = %i : priority = %s : state = %i : depCount = %i : dependentId = %i") %
        prefix.c_str() %
        id %
        page->storageId() %
        priorityString.c_str() %
        static_cast<b::uint32>(state) %
        dependencyCount %
        dependentId
        );
}

page_cache_manager::page_cache_manager() :
    _pushEvent(nullptr),
    _loadCompletedEvent(nullptr),
    _cancelAllEvent(nullptr),
    _exitLoadingEvent(nullptr),
    _pagesMRU(constants::general::pageModelCacheCapacity()),
    _maxWorkerThreadCount(constants::general::pageModelThreadPoolSize()),
    _loadBlocked(false),
    _typeToken(1000)
{
    _pushEvent = ::CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
    _loadCompletedEvent = ::CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
    _cancelAllEvent = ::CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);
    _exitLoadingEvent = ::CreateEventEx(nullptr, nullptr, 0, EVENT_ALL_ACCESS);

    // create the thread pool events...
    for (size_t i = 0 ; i < _maxWorkerThreadCount ; ++i)
    {
        _threadReady.push_back(CreateEventEx(nullptr, nullptr, CREATE_EVENT_INITIAL_SET, EVENT_ALL_ACCESS));
    }

    // connect the mru purge handler
    _pageMRUPurgeFunction = 
        std::bind(
            &page_cache_manager::unloadPage,
            this,
            std::placeholders::_1,
            std::placeholders::_2);

    _pagesMRU.setPurgeHandler(_pageMRUPurgeFunction);

    _typeToken = constants::memoryTrackingService()->registerType(L"model cache manager", false);
    constants::memoryTrackingService()->logCreate(_typeToken, 0, static_cast<b::uint32>(_pagesMRU.size()));
}

page_cache_manager::~page_cache_manager()
{
    // destruct the thread pool events...
    for (auto &handle : _threadReady)
    {
        CloseHandle(handle);
    }
}

void page_cache_manager::scheduleCommand(
    std::shared_ptr<command_object> commandObject)
{
    BAJA_LOCK(_mutex);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::scheduleCommand(id = %i, pageId = %i)") %
        commandObject->id %
        commandObject->page->storageId()
        );

    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(commandObject->page->storageId());

    if (_pagesCommandsMap.end() == pageCommandsIt)
    {
        std::vector<std::shared_ptr<command_object>> pageCommands;
        _pagesCommandsMap[commandObject->page->storageId()] = pageCommands;
    }

    _pagesCommandsMap[commandObject->page->storageId()].push_back(commandObject);

    _commandsQueue.push(priority_command_pointer(commandObject));

    SetEvent(_pushEvent);
}

void page_cache_manager::onCommandCompleted(
    priority_command_pointer command)
{
    BAJA_LOCK(_mutex);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::onCommandCompleted(id = %i, pageId = %i)") %
        command.priorityObjectPointer->id %
        command.priorityObjectPointer->page->storageId()
        );

    b::uint32 pageId = command.priorityObjectPointer->page->storageId();
    b::uint32 commandId = command.priorityObjectPointer->id;
    b::uint32 dependentCommandId = command.priorityObjectPointer->dependentId;

    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsMapIt;
    pageCommandsMapIt = _pagesCommandsMap.find(pageId);

    if (_pagesCommandsMap.end() == pageCommandsMapIt)
    {
        // no commands vector for this page...
        return;
    }

    // remove from list, and update dependents...
    std::vector<std::shared_ptr<command_object>>& pageCommands = _pagesCommandsMap[pageId];
    std::vector<std::shared_ptr<command_object>>::iterator pageCommandsIt;
    for (pageCommandsIt = pageCommands.begin() ; pageCommandsIt < pageCommands.end() ; ++pageCommandsIt)
    {
        if ((*pageCommandsIt)->id == commandId)
        {
            pageCommands.erase(pageCommandsIt);
            break;
        }
    }
    for (pageCommandsIt = pageCommands.begin() ; pageCommandsIt < pageCommands.end() ; ++pageCommandsIt)
    {
        if ((*pageCommandsIt)->id == dependentCommandId)
        {
            (*pageCommandsIt)->dependencyCount--;

            DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                boost::wformat(L"!!!! page_cache_manager::onCommandCompleted(id = %i, pageId = %i) : marked command %i to have one less dependency (current = %i)") %
                command.priorityObjectPointer->id %
                command.priorityObjectPointer->page->storageId() %
                (*pageCommandsIt)->id %
                (*pageCommandsIt)->dependencyCount
                );
        }
    }

    if (command.priorityObjectPointer->targetLevel == page_level::page_deleted)
    {
        // the page has been deleted.  Remove the command entries for this map.
        _pagesCommandsMap.erase(pageId);
    }
}

void page_cache_manager::scheduleFullLoad(
    std::shared_ptr<ipage_model> page,
    bool nextCommand)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::scheduleFullLoad(pageId = %i)") %
        page->storageId()
        );

    std::shared_ptr<command_object> inObject0 = std::make_shared<command_object>();
    inObject0->page = page;
    inObject0->targetLevel = page_level::page_full;
    inObject0->dependencyCount = 0;
    inObject0->priority = (nextCommand == false) ? priority_type::priority_load_pass_0 : priority_type::priority_next_command_0;
    inObject0->dump(L"vv pushing");

    std::shared_ptr<command_object> inObject1 = std::make_shared<command_object>();
    inObject1->page = page;
    inObject1->targetLevel = page_level::page_full;
    inObject1->dependencyCount = 0;
    inObject1->priority = (nextCommand == false) ? priority_type::priority_load_pass_1 : priority_type::priority_next_command_1;
    inObject1->dump(L"vv pushing");

    std::shared_ptr<command_object> inObject2 = std::make_shared<command_object>();
    inObject2->page = page;
    inObject2->targetLevel = page_level::page_full;
    inObject2->dependencyCount = 0;
    inObject2->priority = (nextCommand == false) ? priority_type::priority_load_pass_2 : priority_type::priority_next_command_2;
    inObject2->dump(L"vv pushing");

    inObject0->dependencyCount = 0;             // command0 does not depend on any other command.
    inObject0->dependentId = inObject1->id;     // command1 depends on command0.

    inObject1->dependencyCount = 1;             // command1 depends on 1 other command.
    inObject1->dependentId = inObject2->id;     // command2 depends on command1.

    inObject2->dependencyCount = 1;             // commnad2 depends on 1 other command.
    inObject2->dependentId = 0;                 // nobody depends on command2.

    scheduleCommand(inObject0);
    scheduleCommand(inObject1);
    scheduleCommand(inObject2);
}

void page_cache_manager::updateCommandsStates_SelfSelf(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes,
    bool& stopLoading)
{
    BAJA_LOCK(_mutex);

    // [B] == self
    // [E] == self

    action = action_type::action_load;
    schedule = schedule_type::schedule_immediate;
    passes = load_passes::load_0;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfSelf(pageId = %i)") %
        pageId
        );

    // what about current queued/running commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                (*commandObjectIt)->state = command_state::command_cancelled;

                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfSelf(pageId = %i) : cancelled command id = %i") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                schedule = schedule_type::schedule_delayed_high;
                stopLoading = true;

                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfSelf(pageId = %i) : loading in progress, stop loading.") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_SelfFull(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes)
{
    BAJA_LOCK(_mutex);

    // [B] == self
    // [E] == full

    action = action_type::action_load;
    schedule = schedule_type::schedule_delayed_normal;
    passes = load_passes::load_0_1_2;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfFull(pageId = %i)") %
        pageId
        );

    // what about current queued commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                if ( (*commandObjectIt)->targetLevel == page_level::page_full )
                {
                    // already queued...
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfFull(pageId = %i) : already queued as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );

                    break;
                }
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                if ( (*commandObjectIt)->targetLevel == page_level::page_full )
                {
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfFull(pageId = %i) : already started as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );

                    break;
                }
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_FullFull(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes)
{
    BAJA_LOCK(_mutex);

    // [B] == full
    // [E] == full

    action = action_type::action_load;
    schedule = schedule_type::schedule_immediate;
    passes = load_passes::load_0_1_2;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullFull(pageId = %i)") %
        pageId
        );

    // what about current queued commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                (*commandObjectIt)->state = command_state::command_cancelled;

                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullFull(pageId = %i) : cancelled command id = %i") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                schedule = schedule_type::schedule_delayed_normal;

                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullFull(pageId = %i) : in progress id = %i") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_FullSelf(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes)
{
    BAJA_LOCK(_mutex);

    // [B] == full
    // [E] == self

    action = action_type::action_load;
    schedule = schedule_type::schedule_immediate;
    passes = load_passes::load_0;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullSelf(pageId = %i)") %
        pageId
        );

    // what about current queued commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                (*commandObjectIt)->state = command_state::command_cancelled;

                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullSelf(pageId = %i) : cancelled command id = %i") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                schedule = schedule_type::schedule_delayed_normal;
                DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                    boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullSelf(pageId = %i) : in progress id = %i") %
                    pageId %
                    (*commandObjectIt)->id
                    );
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_SelfThumbnail(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes,
    bool& stopLoading)
{
    BAJA_LOCK(_mutex);

    // [B] == self
    // [E] == thumbnail

    action = action_type::action_load;
    schedule = schedule_type::schedule_delayed_normal;
    passes = load_passes::load_0;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfThumbnail(pageId = %i)") %
        pageId
        );

    // what about current queued commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                if ( (*commandObjectIt)->targetLevel == page_level::page_full )
                {
                    // full is queued, cancel it.
                    (*commandObjectIt)->state = command_state::command_cancelled;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfThumbnail(pageId = %i) : cancelled command id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
                if ( (*commandObjectIt)->targetLevel == page_level::page_thumbnail )
                {
                    // already scheduled, no need to do anything.
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfThumbnail(pageId = %i) : already queued as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                if ( (*commandObjectIt)->targetLevel == page_level::page_full )
                {
                    // full is runnin, load thumbnail right after...
                    schedule = schedule_type::schedule_delayed_high;
                    stopLoading = true;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfThumbnail(pageId = %i) : already started command id = %i, stop loading.") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
                if ( (*commandObjectIt)->targetLevel == page_level::page_thumbnail )
                {
                    // already running, no need to do anything.
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfThumbnail(pageId = %i) : already in progress as command id = %i.") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_FullThumbnail(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes)
{
    BAJA_LOCK(_mutex);

    // [B] == full
    // [E] == thumbnail

    action = action_type::action_load;
    schedule = schedule_type::schedule_delayed_normal;
    passes = load_passes::load_0;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_FullThumbnail(pageId = %i)") %
        pageId
        );

    // what about current queued commands?
    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            // There can be more than one queued command in case of load/full/pass.
            // did any of them start?
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                // a thumbnail is already queued
                if ( (*commandObjectIt)->targetLevel == page_level::page_thumbnail )
                {
                    // already scheduled, no need to do anything.
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfSelf(pageId = %i) : already queued as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
            }
            else if ( (*commandObjectIt)->state == command_state::command_started)
            {
                // [D]
                // a thumbnail is in progress
                if ( (*commandObjectIt)->targetLevel == page_level::page_thumbnail )
                {
                    // already running, no need to do anything.
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_SelfSelf(pageId = %i) : already running as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
            }
        }
    }
}

void page_cache_manager::updateCommandsStates_XDeleted(
    b::uint32 pageId,
    action_type& action,
    schedule_type& schedule,
    load_passes& passes)
{
    BAJA_LOCK(_mutex);

    // [B] == full/self
    // [E] == deleted

    action = action_type::action_delete;
    schedule = schedule_type::schedule_delayed_normal;
    passes = load_passes::load_0;

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_XDeleted(pageId = %i)") %
        pageId
        );

    std::map<b::uint32, std::vector<std::shared_ptr<command_object>>>::iterator pageCommandsIt;
    pageCommandsIt = _pagesCommandsMap.find(pageId);
    if (_pagesCommandsMap.end() != pageCommandsIt)
    {
        // This page has other commands queued or in progress...
        std::vector<std::shared_ptr<command_object>>::iterator commandObjectIt;
        for (commandObjectIt = pageCommandsIt->second.begin(); commandObjectIt < pageCommandsIt->second.end(); ++commandObjectIt)
        {
            if ( (*commandObjectIt)->state == command_state::command_not_started)
            {
                // [C]
                if ( (*commandObjectIt)->targetLevel == page_level::page_deleted )
                {
                    // already queued...
                    action = action_type::action_noop;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_XDeleted(pageId = %i) : already queued as id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
                else
                {
                    (*commandObjectIt)->state = command_state::command_cancelled;

                    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                        boost::wformat(L"!!!! page_cache_manager::updateCommandsStates_XDeleted(pageId = %i) : cancelled queued command id = %i") %
                        pageId %
                        (*commandObjectIt)->id
                        );
                }
            }
        }
    }
}

void page_cache_manager::push(
    std::shared_ptr<ipage_model> page,
    page_level targetLevel) // [E]
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::push(pageId = %i, targetLevel = %i)") %
        page->storageId() %
        static_cast<b::uint32>(targetLevel)
        );

    _pagesMRU.tryAdd(page->storageId(), page, false);

    constants::memoryTrackingService()->logExpand(_typeToken, 0, static_cast<b::uint32>(_pagesMRU.size()));

    action_type action = action_type::action_load;
    schedule_type schedule = schedule_type::schedule_immediate;
    load_passes passes = load_passes::load_0;

    bool stopLoading = false;

    page_level currentLevel = page->loadLevel(); // [B]

    if (currentLevel == page_level::page_none)
    {
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
            boost::wformat(L"!!!! page_cache_manager::push(pageId = %i, targetLevel = %i) : converting current level page_none -> page_self") %
            page->storageId() %
            static_cast<b::uint32>(targetLevel)
            );
        currentLevel = page_level::page_self;
    }

    if (currentLevel == page_level::page_self &&targetLevel == page_level::page_self)
    {
        updateCommandsStates_SelfSelf(page->storageId(), action, schedule, passes, stopLoading);
    }
    if (currentLevel == page_level::page_self && targetLevel == page_level::page_full)
    {
        updateCommandsStates_SelfFull(page->storageId(), action, schedule, passes);
    }
    if (currentLevel == page_level::page_full && targetLevel == page_level::page_full)
    {
        updateCommandsStates_FullFull(page->storageId(), action, schedule, passes);
    }
    if (currentLevel == page_level::page_full && targetLevel == page_level::page_self)
    {
        updateCommandsStates_FullSelf(page->storageId(), action, schedule, passes);
    }
    if (currentLevel == page_level::page_self && targetLevel == page_level::page_thumbnail)
    {
        updateCommandsStates_SelfThumbnail(page->storageId(), action, schedule, passes, stopLoading);
    }
    if (currentLevel == page_level::page_full && targetLevel == page_level::page_thumbnail)
    {
        updateCommandsStates_FullThumbnail(page->storageId(), action, schedule, passes);
    }
    if (targetLevel == page_level::page_deleted)
    {
        updateCommandsStates_XDeleted(page->storageId(), action, schedule, passes);
    }

    std::wstring resultString;
    switch (action)
    {
    case action_type::action_noop: resultString += L"action : noop ";
        break;
    case action_type::action_load: resultString += L"action : load ";
        break;
    case action_type::action_delete: resultString += L"action : delete ";
        break;
    }
    switch (schedule)
    {
    case schedule_type::schedule_immediate: resultString += L", schedule : immediate";
        break;
    case schedule_type::schedule_delayed_normal: resultString += L", schedule : schedule_delayed_normal";
        break;
    case schedule_type::schedule_delayed_high: resultString += L", schedule : schedule_delayed_high";
        break;
    }
    switch (passes)
    {
    case load_passes::load_0: resultString += L", passes : load_0";
        break;
    case load_passes::load_0_1_2: resultString += L", passes : load_0_1_2";
        break;
    }

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::push(pageId = %i, targetLevel = %i) => %s") %
        page->storageId() %
        static_cast<b::uint32>(targetLevel) %
        resultString.c_str()
        );

    if ((action != action_type::action_load) && (action != action_type::action_delete))
    {
        return;
    }

    std::shared_ptr<iinternal_page_model> pageInternal = std::dynamic_pointer_cast<iinternal_page_model>(page);

    if (stopLoading == true)
    {
        pageInternal->stopLoading();
    }

    // This conversion is because the view code does not expect 
    // call backs from within the scheduling (too early).
    if (schedule == schedule_type::schedule_immediate)
    {
        schedule = schedule_type::schedule_delayed_high;
    }

    if (schedule == schedule_type::schedule_immediate)
    {
        if (passes == load_passes::load_0)
        {
            pageInternal->load(0, targetLevel);
        }
        else if (passes == load_passes::load_0_1_2)
        {
            pageInternal->load(0, targetLevel);
            pageInternal->load(1, targetLevel);
            pageInternal->load(2, targetLevel);
        }
    }
    else if (schedule == schedule_type::schedule_delayed_normal)
    {
        if (action == action_type::action_load)
        {
            if (passes == load_passes::load_0)
            {
                std::shared_ptr<command_object> inObject = std::make_shared<command_object>();
                inObject->page = page;
                inObject->dependencyCount = 0;
                inObject->targetLevel = targetLevel;
                inObject->priority = priority_thumbnail;
                inObject->dump(L"vv pushing");
                scheduleCommand(inObject);
            }
            else if (passes == load_passes::load_0_1_2)
            {
                scheduleFullLoad(page, false); // calls scheduleCommand 3 times.
            }
        }
        else if (action == action_type::action_delete)
        {
            std::shared_ptr<command_object> inObject = std::make_shared<command_object>();
            inObject->page = page;
            inObject->dependencyCount = 0;
            inObject->targetLevel = targetLevel;
            inObject->priority = priority_next_command_0;
            inObject->dump(L"vv pushing");
            scheduleCommand(inObject);
        }
    }
    else if (schedule == schedule_type::schedule_delayed_high)
    {
        if (passes == load_passes::load_0)
        {
            std::shared_ptr<command_object> inObject = std::make_shared<command_object>();
            inObject->page = page;
            inObject->dependencyCount = 0;
            inObject->targetLevel = targetLevel;
            inObject->priority = priority_next_command_0;
            inObject->dump(L"vv pushing");
            scheduleCommand(inObject);
        }
        else if (passes == load_passes::load_0_1_2)
        {
            scheduleFullLoad(page, true); // calls scheduleCommand 3 times.
        }

    }
}

bool page_cache_manager::pop(
    bool& readyFound,
    priority_command_pointer& outCommand)
{
    BAJA_LOCK(_mutex);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::pop() : _commandsQueue.size() = %i") %
        _commandsQueue.size()
        );

    size_t itemCount = _commandsQueue.size();

    if (itemCount == 0)
    {
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
            boost::wformat(L"!!!! page_cache_manager::pop() : nothing is queued - returning...")
            );

        return false;
    }

    // nothing new should be pushed while we are here...

    priority_command_pointer commandPtr;
    readyFound = false;

    // look for a ready command...
    std::priority_queue<priority_command_pointer> tempCommandsQueue;
    while ((readyFound == false) && (itemCount != 0))
    {
        commandPtr = _commandsQueue.top();
        _commandsQueue.pop();

        commandPtr.priorityObjectPointer->dump(L"page_cache_manager::pop");

        if (commandPtr.priorityObjectPointer->state != command_state::command_cancelled)
        {
            if (commandPtr.priorityObjectPointer->dependencyCount == 0)
            {
                readyFound = true;
            }
            else
            {
                tempCommandsQueue.push(commandPtr);
            }
        }
        // else, we don't need to keep this command around since it's either cancelled or finished.

        --itemCount;
    }

    // put the non-ready commands back...
    while (tempCommandsQueue.size() != 0)
    {
        priority_command_pointer tempObjectPtr = tempCommandsQueue.top();
        tempCommandsQueue.pop();
        _commandsQueue.push(tempObjectPtr);
    }

    if (readyFound)
    {
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
            boost::wformat(L"!!!! page_cache_manager::pop() : item found...")
            );

        commandPtr.priorityObjectPointer->state = command_state::command_started;
        commandPtr.priorityObjectPointer->dump(L"^^ popping");

        outCommand = commandPtr;
    }
    else
    {
        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
            boost::wformat(L"!!!! page_cache_manager::pop() : no items are ready...")
            );
        // is there anything left?
        if (_commandsQueue.size() == 0)
        {
            DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
                boost::wformat(L"!!!! page_cache_manager::pop() : no items are queued [2]...")
                );

            return false;
        }
    }

    return true;
}

void page_cache_manager::clear()
{
    SetEvent(_cancelAllEvent);
}

void page_cache_manager::stopAsync()
{
    SetEvent(_exitLoadingEvent);
}

void page_cache_manager::onTaskCompleted(
    priority_command_pointer command,
    b::int32 threadIndex
    )
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::onTaskCompleted() : command->id = %i...") %
        command.priorityObjectPointer->id
        );

    onCommandCompleted(command);

    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::onTaskCompleted() : signaling loadCompletedEvent & threadReady")
        );

    SetEvent(_loadCompletedEvent);
    SetEvent(_threadReady[threadIndex]);
}

void page_cache_manager::executeTask(
    priority_command_pointer command,
    b::int32 threadIndex
    )
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::executeTask() : command->id = %i") %
        command.priorityObjectPointer->id
        );

    b::uint32 pass = 0;
    switch (command.priorityObjectPointer->priority)
    {
    case priority_type::priority_last           : pass = 0; break;
    case priority_type::priority_next_command_0 : pass = 0; break;
    case priority_type::priority_next_command_1 : pass = 1; break;
    case priority_type::priority_next_command_2 : pass = 2; break;
    case priority_type::priority_thumbnail      : pass = 0; break;
    case priority_type::priority_load_pass_0    : pass = 0; break;
    case priority_type::priority_load_pass_1    : pass = 1; break;
    case priority_type::priority_load_pass_2    : pass = 2; break;
    default:
        BAJA_ASSERT(false, L"an unsupported load priority/pass");
    }

    std::shared_ptr<iinternal_page_model> pageInternal = std::dynamic_pointer_cast<iinternal_page_model>(command.priorityObjectPointer->page);
    if (command.priorityObjectPointer->targetLevel == page_level::page_deleted)
    {
        // the page is getting deleted.  Remove the from MRU cache.
        _pagesMRU.remove(command.priorityObjectPointer->page->storageId());
        pageInternal->dispose();
    }
    else
    {
        pageInternal->load(pass, command.priorityObjectPointer->targetLevel);
    }
    onTaskCompleted(command, threadIndex);
}

void page_cache_manager::executeTaskAsync(
    priority_command_pointer command,
    b::int32 threadIndex
    )
{
    page_cache_manager* p_this = this;

    concurrency::task<void> t(
            [this, command, threadIndex]()
            {
            this->executeTask(command, threadIndex);
            }
        );
}

void page_cache_manager::unloadPage(
    b::uint32 pageId,
    std::shared_ptr<ipage_model> page)
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"Page MRU : page_cache_manager::unloadPage(pageId = %i) : unloading...") %
        pageId
        );

    constants::memoryTrackingService()->logShrink(_typeToken, 0, static_cast<b::uint32>(_pagesMRU.size()));

    concurrency::task<void> t(
            [page]()
            {
                // not that this might en-up
                std::shared_ptr<iinternal_page_model> pageToUnload = std::dynamic_pointer_cast<iinternal_page_model>(page);
                pageToUnload->unload();
            }
        );

}

void page_cache_manager::processQueued()
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::processQueued()")
        );

    bool readyFound = false;
    priority_command_pointer currentCommand;

    while ( pop(readyFound, currentCommand) )
    {
        if (readyFound == false)
        {
            // reset non-ready...
            _loadBlocked = true;
            break;
        }

        // wait for a thread to be available...
        DWORD ret = WaitForMultipleObjectsEx(static_cast<DWORD>(_maxWorkerThreadCount), &(_threadReady[0]), FALSE, INFINITE, FALSE);
        DWORD threadIndex = ret - WAIT_OBJECT_0;

        executeTaskAsync(currentCommand, threadIndex);
    }
}

void page_cache_manager::cancelAllQueued()
{
    BAJA_LOCK(_mutex);

    while (_commandsQueue.size() != 0)
    {
        _commandsQueue.pop();
    }

    _pagesCommandsMap.clear();
}

void page_cache_manager::run()
{
    DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
        boost::wformat(L"!!!! page_cache_manager::run()")
        );

    HANDLE events[4] = {
            _pushEvent,
            _cancelAllEvent,
            _exitLoadingEvent,
            _loadCompletedEvent
    };

    bool done = false;

    while (done == false)
    {
        // Enter the wait state...
        DWORD ret = WaitForMultipleObjectsEx(sizeof(events) / sizeof(HANDLE), events, FALSE, INFINITE, FALSE);
        DWORD eventIndex = ret - WAIT_OBJECT_0;

        DEBUG_CONSOLE_TRACE_TAG(TRACE_TAG_MODEL_CACHE_MGR,
            boost::wformat(L"!!!! page_cache_manager::run() : event %i has been signaled.") %
            eventIndex
            );

        switch (eventIndex)
        {
        case 0: // _pushEvent
            processQueued();
            break;
        case 1: // _cancelAllEvent
            cancelAllQueued();
            break;
        case 2: // _exitLoadingEvent
            done = true;
            break;
        case 3: // _loadCompletedEvent
            if (_loadBlocked == true)
            {
                // somebody finished, maybe we can pump more...
                _loadBlocked = false;
                processQueued();
            }
            break;
        }
    }
}

void page_cache_manager::runAsync()
{
    page_cache_manager* p_this = this;

    concurrency::task<void> t([p_this]()
    {
        p_this->run();
    });
}
